#include <string.h>
#include "lcd_bsp.h"

#include "jdi_mip_display.h"

#define CMD_NO_UPDATE 0x00
#define CMD_BLINKING_BLACK 0x10
#define CMD_BLINKING_WHITE 0x18
#define CMD_BLINKING_INVERSION 0x14
#define CMD_ALL_CLEAR 0x20
#define CMD_VCOM 0x40
#define CMD_3BIT_UPDATE 0x80
#define CMD_1BIT_UPDATE 0x88
#define CMD_4BIT_UPDATE 0x90

typedef struct
{
    void (*spi_write)(uint8_t *data, int len);
    void (*spi_cs)(uint8_t enable);
    void (*disp_set)(uint8_t enable);
} jdiMipDisplay_t;

static jdiMipDisplay_t _jdi;

static uint16_t _background = COLOR_BLACK;

uint8_t _back_buffer[BUFF_HEIGHT * BUFF_WIDTH];
#ifdef DIFF_LINE_UPDATE
uint8_t _disp_buffer[BUFF_HEIGHT * BUFF_WIDTH];
#endif

static uint8_t compareBuffersLine(int lineIndex)
{
#ifdef DIFF_LINE_UPDATE

    for (int i = 0; i < BUFF_HEIGHT; i++)
    {
        int pixelIdx = lineIndex + i;
        if (_back_buffer[pixelIdx] != _disp_buffer[pixelIdx])
            return 0;
    }
#endif
    return 1;
}

static void sendLineCommand(uint8_t *line_data, uint8_t line)
{
    if ((line < 0) || (line >= BUFF_WIDTH))
        return;

    uint8_t buf[2];
#ifdef MONO_COLOR_DISPLAY
    buf[0] = CMD_1BIT_UPDATE;
#else
    buf[0] = CMD_4BIT_UPDATE;
#endif
    buf[1] = line + 1;
    _jdi.spi_cs(1);
    _jdi.spi_write(buf, 2);
    _jdi.spi_write(line_data, BUFF_HEIGHT);
    buf[0] = 0x00;
    buf[1] = 0x00;
    _jdi.spi_write(buf, 2);
    _jdi.spi_cs(0);
}

void displayInit(void)
{
    lcdInit();
    _jdi.disp_set = lcdDispSet;
    _jdi.spi_cs = lcdCSSet;
    _jdi.spi_write = lcdWrite;

    memset(_back_buffer, (uint8_t)((_background & 0x0F) | ((_background & 0x0F) << 4)), sizeof(_back_buffer));
#ifdef DIFF_LINE_UPDATE
    memset(_disp_buffer, (uint8_t)((_background & 0x0F) | ((_background & 0x0F) << 4)), sizeof(_disp_buffer));
#endif
    displayClearScreen();
    displayOn();

    LOG_I("jdiMipDisplayInit success");
}

void displayDeinit(void)
{
    lcdDeInit();
}

void displayOn(void)
{
    _jdi.disp_set(1);
}

void displayOff(void)
{
    _jdi.disp_set(0);
}

void displayRefresh(void)
{
    for (int i = 0; i < BUFF_WIDTH; i++)
    {
        int lineIdx = BUFF_HEIGHT * i;
        uint8_t *line_cmd;
#ifdef DIFF_LINE_UPDATE
        if (compareBuffersLine(lineIdx) == 1)
            continue;
        memcpy(&_disp_buffer[lineIdx], &_back_buffer[lineIdx], BUFF_HEIGHT);
        line_cmd = &_disp_buffer[lineIdx];
#else
        line_cmd = &_back_buffer[lineIdx];
#endif
        sendLineCommand(line_cmd, i);
    }
    /* 清空缓存 */
    memset(_back_buffer, (uint8_t)((_background & 0x0F) | ((_background & 0x0F) << 4)), sizeof(_back_buffer));
}

void displayClearScreen(void)
{
    uint8_t buf[2];
    buf[0] = CMD_ALL_CLEAR;
    buf[1] = 0x00;
    _jdi.spi_cs(1);
    _jdi.spi_write(buf, 2);
    _jdi.spi_cs(0);
}

void displayBlinkTest(uint8_t white)
{
    uint8_t buf[2];
    if (white)
        buf[0] = CMD_BLINKING_WHITE;
    else
        buf[0] = CMD_BLINKING_BLACK;
    buf[1] = 0;
    _jdi.spi_cs(1);
    _jdi.spi_write(buf, 2);
    _jdi.spi_cs(0);
}

void displayInversionSet(uint8_t enable)
{
    uint8_t buf[2];
    if (enable == 1)
        buf[0] = CMD_BLINKING_INVERSION;
    else
        buf[0] = CMD_NO_UPDATE;
    buf[1] = 0x00;
    _jdi.spi_cs(1);
    _jdi.spi_write(buf, 2);
    _jdi.spi_cs(0);
}

void drawPoint(int16_t x, int16_t y, uint8_t color)
{
    if (x < 0 || x >= BUFF_WIDTH || y < 0 || y >= DISPLAY_HEIGHT)
        return;

    /* 向左转置90度绘制 */
    int16_t x1 = DISPLAY_HEIGHT - y;
    int16_t y1 = x;

#ifdef MONO_COLOR_DISPLAY
    int pixelIdx = (BUFF_HEIGHT * y1) + (x1 / 8);
    _back_buffer[pixelIdx] &= ~(0x80 >> (x1 % 8));
    _back_buffer[pixelIdx] |= ((color & 0x80) >> (x1 % 8));
#else
    int pixelIdx = (BUFF_HEIGHT * y1) + (x1 / 2);
    if (x1 % 2 == 0)
    {
        _back_buffer[pixelIdx] &= 0x0F;
        _back_buffer[pixelIdx] |= (color & 0x0F) << 4;
    }
    else
    {
        _back_buffer[pixelIdx] &= 0xF0;
        _back_buffer[pixelIdx] |= color & 0x0F;
    }
#endif
}
